﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Ionic.Zlib;

namespace FSNCleaner
{
    /// <summary>
    /// Enumère les encryptages disponibles pour une archive XP3.
    /// </summary>
    public enum Encryption
    {
        None,
        FateFull,
        FateTrial
    }

    /// <summary>
    /// Regroupement des fonctions de création d'archive XP3.
    /// </summary>
    static public class XP3
    {
        /// <summary>
        /// Le nombre premier immédiatement inférieur à 65536
        /// </summary>
        const uint BASE = 65521;
        /// <summary>
        /// Calcul l'ADLER32 (équivalent du CRC) du buffer spécifié.
        /// </summary>
        /// <param name="buffer">The buffer.</param>
        /// <param name="offset">The offset.</param>
        /// <param name="count">The count.</param>
        /// <returns></returns>
        static public long Adler32(byte[] buffer, int offset, int count)
        {
            uint s1 = 1;
            uint s2 = 0;

            while (count > 0)
            {
                // We can defer the modulo operation:
                // s1 maximally grows from 65521 to 65521 + 255 * 3800
                // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
                int n = 3800;
                if (n > count)
                {
                    n = count;
                }
                count -= n;
                while (--n >= 0)
                {
                    s1 = s1 + (uint)(buffer[offset++] & 0xff);
                    s2 = s2 + s1;
                }
                s1 %= BASE;
                s2 %= BASE;
            }

            return (s2 << 16) | s1;

        }

        /// <summary>
        /// Encrypts the specified file.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <param name="encryption">The encryption.</param>
        private static void Encrypt(byte[] file, Encryption encryption)
        {
            switch (encryption)
            {
                case Encryption.FateFull:
                    for (int i = 0; i < file.Length; i++) file[i] ^= 0x0036;
                    if (file.Length > 19) file[19] ^= 0x0001;
                    if (file.Length > 191017) file[191017] ^= 0x0003;
                    break;

                case Encryption.FateTrial:
                    for (int i = 0; i < file.Length; i++) file[i] ^= 0x0052;
                    if (file.Length > 30) file[30] ^= 0x0001;
                    if (file.Length > 55355) file[55355] ^= 0x0020;
                    break;

                default:
                    //On fait rien
                    break;
            }
        }

        /// <summary>
        /// Archive le fichier spécifié dans les flux passé en paramètre,
        /// avec l'encryption spécifié
        /// </summary>
        /// <param name="fileFullName">Full name of the file.</param>
        /// <param name="encryption">The encryption.</param>
        /// <param name="isCompressed">if set to <c>true</c> [is compressed].</param>
        /// <param name="fileStream">The file stream.</param>
        /// <param name="bufferStream">The buffer stream.</param>
        /// <param name="bufferWriter">The buffer writer.</param>
        internal static void Repack(string fileFullName, Encryption encryption, bool isCompressed, FileStream fileStream, MemoryStream bufferStream, BinaryWriter bufferWriter)
        {
            var isEncrypted = Convert.ToBoolean(encryption);
            var data = File.ReadAllBytes(fileFullName);
            var dataLength = data.Length;
            var name = Path.GetFileName(fileFullName);
            var nameLength = name.Length;

            Encrypt(data, encryption);
            
            using (var compressedFileStream = new MemoryStream())
            {
                //Compression du fichier, ou simple copie si on veut pas de compression
                if (isCompressed)
                    using (var compressor = new ZlibStream(compressedFileStream, CompressionMode.Compress,
                                                              CompressionLevel.BestCompression, true))
                    {
                        compressor.Write(data, 0, dataLength);
                    }
                else compressedFileStream.Write(data, 0, dataLength);

                //File
                bufferWriter.Write(new byte[] { 0x46, 0x69, 0x6C, 0x65 });
                bufferWriter.Write((ulong)((nameLength) * 2 + 90));

                //info
                bufferWriter.Write(new byte[] { 0x69, 0x6E, 0x66, 0x6F });
                bufferWriter.Write((ulong)(nameLength * 2 + 22));
                bufferWriter.Write((uint)(isEncrypted ? 2147483648 : 0));
                bufferWriter.Write((ulong)dataLength);
                bufferWriter.Write((ulong)compressedFileStream.Length);
                bufferWriter.Write((ushort)nameLength);

                //On ignore le premier octet de synchro de l'unicode
                using (MemoryStream tempStream = new MemoryStream())
                {
                    using (BinaryWriter tempWriter = new BinaryWriter(tempStream, Encoding.Unicode))
                    {
                        tempWriter.Write(name);
                        bufferStream.Write(tempStream.ToArray(), 1, (int)tempStream.Length - 1);
                    }
                }

                //segm
                bufferWriter.Write(new byte[] { 0x73, 0x65, 0x67, 0x6D });
                bufferWriter.Write((ulong)28);
                bufferWriter.Write((uint)(isCompressed ? 1 : 0));
                bufferWriter.Write((ulong)fileStream.Position);
                bufferWriter.Write((ulong)dataLength);
                bufferWriter.Write((ulong)compressedFileStream.Length);

                //adlr
                bufferWriter.Write(new byte[] { 0x61, 0x64, 0x6C, 0x72 });
                bufferWriter.Write((ulong)4);
                bufferWriter.Write((uint)Adler32(data, 0, dataLength));

                //écriture du fichier
                compressedFileStream.WriteTo(fileStream);
            }
        }

        #region Sample
        //Header
        //Hexa: "0x58 0x50 0x33 0x0D 0x0A 0x20 0x0A 0x1A 0x8B 0x67 0x01" => pos 11
        //Save position: positionIndexOffset
        //ulong: 0 => pos 19
        //Hexa: "0x00"

        //ForEach File
        //Save this index in indexBuffer:
        //Hexa: 0x46 0x69 0x6C 0x65 == "File"
        //ulong: 112 => FileName.Length*2 + Segments.Count*28 + 64 => "p" et sept 0
        //Hexa: 0x69 0x6E 0x66 0x6F == "info"
        //ulong: 44 => FileName.Length*2 + 22
        //uint: 0 => IsEncrypted
        //ulong: 15 => original.Length
        //ulong: 15 => compressed.Length
        //ushort: 118 => FileName.Length
        //ushort: 70 => "F"
        //ushort: 105 => "i"
        //ushort: 99 => "c"
        //ushort: 104 => "h"
        //ushort: 105 => "i"
        //ushort: 101 => "e"
        //ushort: 114 => "r"
        //ushort: 49 => "1"
        //ushort: 46 => "."
        //ushort: 107 => "k"
        //ushort: 115 => "s"
        //Hexa: 0x73 0x65 0x67 0x6D == "segm"
        //ulong: 28 => Segments.Count*28
        //uint: 0 => IsCompressed
        //ulong: 20 => offset
        //ulong: 15 => original.Length
        //ulong: 15 => compressed.Length
        //Hexa: 0x61 0x64 0x6C 0x72 == "adlr"
        //ulong: 4 => ??? PROFIT!
        //uint: 644875417 (0x99 0x04 0x70 0x26) => adler
        //Write the file

        //Save position: indexOffset

        //Hexa: "00" => IsCompressed
        //ulong: => original.Length
        //ulong: => compressed.Length
        //Write the indexBuffer

        //Write indexOffset at postionIndexOffset 
        #endregion

        ///// <summary>
        ///// Créer une archive XP3 à partir d'un dossier.
        ///// </summary>
        //static public void Repack(string path, string fileName, Encryption encrypting, bool isCompressed)
        //{
        //    var isEncrypted = Convert.ToBoolean(encrypting);

        //    //On initialise un FileStream avec les options qu'on désire, ceci représentera notre fichier disque
        //    using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
        //    {
        //        //On initialise un MemoryStream, ceci représentera notre buffer mémoire
        //        using (var bufferStream = new MemoryStream())
        //        {
        //            //On utilise des BinaryWriter pour écrire des types dans les flux sans buffer intermédiaire,
        //            //sans se préoccuper de l'encodage puisqu'il fait n'importe quoi de toute façon...
        //            using (BinaryWriter fileWriter = new BinaryWriter(fileStream),
        //                                bufferWriter = new BinaryWriter(bufferStream))
        //            {
        //                #region Header
        //                fileWriter.Write(new byte[] { 0x58, 0x50, 0x33, 0x0D, 0x0A, 0x20, 0x0A, 0x1A, 0x8B, 0x67, 0x01 });

        //                var positionIndexOffset = fileStream.Position;

        //                fileWriter.Write((ulong)0);
        //                fileWriter.Write((byte)0x00);
        //                #endregion

        //                var files = from file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
        //                            select new
        //                            {
        //                                Name = Path.GetFileName(file),
        //                                Bytes = File.ReadAllBytes(file)
        //                            };

        //                foreach (var file in files)
        //                {
        //                    Encrypt(file.Bytes, encrypting);

        //                    using (var compressedFileStream = new MemoryStream())
        //                    {
        //                        //Compression du fichier, ou simple copie si on veut pas de compression
        //                        if (isCompressed)
        //                            using (var compressor = new ZlibStream(compressedFileStream, CompressionMode.Compress,
        //                                                                      CompressionLevel.BestCompression, true))
        //                            {
        //                                compressor.Write(file.Bytes, 0, file.Bytes.Length);
        //                            }
        //                        else compressedFileStream.Write(file.Bytes, 0, file.Bytes.Length);

        //                        //File
        //                        bufferWriter.Write(new byte[] { 0x46, 0x69, 0x6C, 0x65 });
        //                        bufferWriter.Write((ulong)((file.Name.Length) * 2 + 90));

        //                        //info
        //                        bufferWriter.Write(new byte[] { 0x69, 0x6E, 0x66, 0x6F });
        //                        bufferWriter.Write((ulong)(file.Name.Length * 2 + 22));
        //                        bufferWriter.Write((uint)(isEncrypted ? 2147483648 : 0));
        //                        bufferWriter.Write((ulong)file.Bytes.Length);
        //                        bufferWriter.Write((ulong)compressedFileStream.Length);
        //                        bufferWriter.Write((ushort)file.Name.Length);

        //                        //On ignore le premier octet de synchro de l'unicode
        //                        using (MemoryStream tempStream = new MemoryStream())
        //                        {
        //                            using (BinaryWriter tempWriter = new BinaryWriter(tempStream, Encoding.Unicode))
        //                            {
        //                                tempWriter.Write(file.Name);
        //                                bufferStream.Write(tempStream.ToArray(), 1, (int)tempStream.Length - 1);
        //                            }
        //                        }

        //                        //segm
        //                        bufferWriter.Write(new byte[] { 0x73, 0x65, 0x67, 0x6D });
        //                        bufferWriter.Write((ulong)28);
        //                        bufferWriter.Write((uint)(isCompressed ? 1 : 0));
        //                        bufferWriter.Write((ulong)fileStream.Position);
        //                        bufferWriter.Write((ulong)file.Bytes.Length);
        //                        bufferWriter.Write((ulong)compressedFileStream.Length);

        //                        //adlr
        //                        bufferWriter.Write(new byte[] { 0x61, 0x64, 0x6C, 0x72 });
        //                        bufferWriter.Write((ulong)4);
        //                        bufferWriter.Write((uint)Adler32(file.Bytes, 0, file.Bytes.Length));

        //                        //écriture du fichier
        //                        compressedFileStream.WriteTo(fileStream);
        //                    }
        //                }

        //                #region Footer
        //                var indexOffset = fileStream.Position;

        //                //Compression du header
        //                using (MemoryStream compressedHeaderStream = new MemoryStream())
        //                {
        //                    using (Stream compressor = new ZlibStream(compressedHeaderStream, CompressionMode.Compress,
        //                                                              CompressionLevel.BestCompression, true))
        //                    {
        //                        bufferStream.WriteTo(compressor);
        //                    }

        //                    fileWriter.Write((byte)0x01);
        //                    fileWriter.Write((ulong)compressedHeaderStream.Length);
        //                    fileWriter.Write((ulong)bufferStream.Length);
        //                    compressedHeaderStream.WriteTo(fileStream);
        //                }

        //                fileStream.Position = positionIndexOffset;
        //                fileWriter.Write((ulong)indexOffset);
        //                #endregion
        //            }
        //        }
        //    }

        //    return;
        //}
    }
}
